//
//  Api.swift
//  JJToolSwift
//
//  Created by heihei on 2024/5/16.
//

import Foundation

/// 通过泛型转模型统一输出，兼容多版本API
public class ApiModel<T: HandyJSON>: HandyJSON {
    /// 状态码
    public var code: Int = 0

    /// 错误信息
    public var msg: String = ""

    /// 返回原始数据
    public var data: Any?

    /// 返回转模型后的对象
    public var object: T?

    /// 返回转模型后的对象数组
    public var array: [T]?
    
    /// 总数，当分页的时候可以用来判断是否还有数据
    public var total: Int = 0

    private var status: Bool?

    /// response header
    public var headers = HTTPHeaders()
    
    /// 请求的body
    public var body: [String: Any] = [:]

    /// 是否成功请求到数据
    public var isSuccess: Bool { return self.code == Api.kSuccess }
    /// 是否需要登录
    public var isNeedLogin: Bool { return self.code == Api.kNeedLogin }
    /// 是否需要设置密码
    public var isNeedSetPwd: Bool { return self.code == Api.kNeedPwd }

    public required init() { }

    public func mapping(mapper: HelpingMapper) {
        mapper <<<
            status <-- "status"
        mapper <<<
            code <-- "code"
        mapper <<<
            msg <-- "msg"
        mapper <<<
            data <-- "data"
        mapper <<<
            total <-- "total"
    }

    public func didFinishMapping() {
        if let status = status {
            code = status ? 0 : -1
            if !status {
                msg = "\(data ?? "")"
            }
        }
        if let dict = data as? [String: Any?], let totalNum = dict["total"] as? Int {
            total = totalNum
        }
    }

    init(error: Api.Error) {
        code = error.rawValue
        switch error {
        case .noNetwork:
            msg = "error_network"
        case .timeout:
            msg = "error_request_timeout"
        default:
            msg = "error_server_error"
        }
    }
}

/// Api接口层，提供不同的接口服务
public class Api {
    /// 请求失败错误类型
    ///
    /// - noNetwork: 无网络
    /// - timeout: 请求超时
    /// - dataError: 解析数据失败
    /// - serverError: 服务器错误，responseCode != 200
    enum Error: Int {
        case noNetwork = -1
        case timeout = -2
        case dataError = -3
        case serverError = -4
    }

    /// 接口成功码
    public static let kSuccess: Int = 200
    /// 需要登录
    public static let kNeedLogin: Int = 2000
    /// 需要设置密码
    public static let kNeedPwd: Int = 4013

    static let acceptableContentTypes = ["application/json", "text/json", "text/plain", "text/javascript","text/html", "text/plain charset=utf-8",] // text/plain用来支持Charles Local Map

    private static let serializationQueue = DispatchQueue(label: "hyhh.session.serializationQueue", qos: .default, attributes: .concurrent)

    static let `default`: Alamofire.Session = {
        let configuration = URLSessionConfiguration.default
        configuration.httpAdditionalHeaders = Alamofire.HTTPHeaders.default.dictionary
        configuration.timeoutIntervalForRequest = 25
        var serverTrustPolicies: [String: ServerTrustEvaluating] = [:]
        /**
         SSL安全认证：与服务器建立安全连接需要对服务器进行验证，可以用证书或者公钥私钥来实现
         该网络框架支持的证书类型：[".cer", ".CER", ".crt", ".CRT", ".der", ".DER"]
         1、DefaultTrustEvaluator 默认策略
         2、SSL Pinning阻止中间人Charles攻击
            - PinnedCertificatesTrustEvaluator 内置证书，将证书放入app的bundle里
            - PublicKeysTrustEvaluator 内置公钥，将证书的公钥硬编码进代码里
         3、DisabledEvaluator 不验证
         然并卵 - 我们公司的网络连接并没有SSL安全认证，强烈吐槽
         */
        // TODO:认证不通过，暂时去掉
//        ["api.xxx.com"].compactMap{ HttpDnsService.sharedInstance()?.getIpByHostAsync($0) }.forEach{ serverTrustPolicies[$0] = DisabledEvaluator() }
//        return Alamofire.Session(configuration: configuration, serverTrustManager: ServerTrustManager(evaluators: serverTrustPolicies))
        let requestQueue = DispatchQueue(label: "hyhh.session.requestQueue", qos: .default, attributes: .concurrent)
        return Alamofire.Session(configuration: configuration,
                                 requestQueue: requestQueue,
                                 serializationQueue: serializationQueue)
    }()

    /// 带模型转换的网络请求，模型是对象Object
    ///
    /// - Parameters:
    ///   - urlRequest: 自定义请求对象
    /// - Returns: DataRequest，无网络时不执行请求返回nil
    @discardableResult
    public class func request(_ urlRequest: URLRequestConvertible) -> DataRequest? {
        if let isReachable = NetworkReachabilityManager()?.isReachable, !isReachable {
            JJHUD.show(status: .failure, text: "error_network")
            return nil
        }
        return Api.default.request(urlRequest).validate(statusCode: [200])
    }

    /// 带模型转换的网络请求，模型是对象Object
    ///
    /// - Parameters:
    ///   - urlRequest: 自定义请求对象
    ///   - keyPath: 对象路径keyPath，是从data后面key开始算起的
    ///   - completionHandler: 完成回调
    /// - Returns: DataRequest，无网络时不执行请求返回nil
    @discardableResult
    public class func request<T: HandyJSON>(_ urlRequest: URLRequestConvertible, keyPath: String? = nil, completionHandler: @escaping(_ result: ApiModel<T>) -> Void) -> DataRequest? {
        // 1、检查网络是否连接，未连接直接返回错误并提示
        if let isReachable = NetworkReachabilityManager.default?.isReachable, !isReachable {
            completionHandler(ApiModel(error: .noNetwork))
            DispatchQueue.main.async { NetErrorView.show() }
            return nil
        }
        // 2、开始请求数据
        return Api.default.request(urlRequest).validate(statusCode: [200]).validate(contentType: acceptableContentTypes)
            .responseJSON(queue: serializationQueue) { response in
            switch response.result {
            case .success(let value):
                JJApiLog("😂😂\(String(describing: response.metrics)) url-\(String(describing: urlRequest)) value-\(value) 🚀end")
                var result = ApiModel<T>(error: .dataError)
                if let value = value as? [String: Any] {
                    if let model = ApiModel<T>.deserialize(from: wrapper(value: value)) {
                        model.object = T.deserialize(from: model.data as? [String: Any], designatedPath: keyPath)
                        if model.object == nil, let jsonObj = model.data,
                           let data = try? JSONSerialization.data(withJSONObject: jsonObj, options: .fragmentsAllowed),
                           let string = String(data: data, encoding: .utf8) {
                            model.array = [T].deserialize(from: string, designatedPath: keyPath)?.compactMap({ $0 })
                        }
                        result = model
                    }
                    if let headers = response.response?.headers {
                        result.headers = headers
                    }
                    if let body = response.request?.httpBody, let string = String(data: body, encoding: .utf8), let dict = string.toDictionary() {
                        result.body = dict
                    }
                    DispatchQueue.main.async {
                        completionHandler(result)
                        if !result.isSuccess, result.isNeedLogin {
                            // 需要登录，发送通知清除本地存储的个人信息，显示登录页
                            NotificationCenter.default.post(name: Notification.Name.User.tokenInvalid, object: nil)
                        }
                        if !result.isSuccess, result.isNeedSetPwd {
                            // 当前账号已存在并且没有密码，需要设置密码
                            if let phone = result.body["tel"] as? String {
                                NotificationCenter.default.post(name: Notification.Name.User.setPwd, object: phone)
                            }
                        }
                    }
                } else {
                    if let value = value as? [Any] {
                        let wrapperValue: [String: Any] = ["code": 0, "msg": "success", "data": value]
                        if let model = ApiModel<T>.deserialize(from: wrapperValue) {
                            model.object = T.deserialize(from: model.data as? [String: Any], designatedPath: keyPath)
                            if model.object == nil, let jsonObj = model.data,
                               let data = try? JSONSerialization.data(withJSONObject: jsonObj, options: .fragmentsAllowed),
                               let string = String(data: data, encoding: .utf8) {
                                model.array = [T].deserialize(from: string, designatedPath: keyPath)?.compactMap({ $0 })
                            }
                            result = model
                        }
                        if let headers = response.response?.headers {
                            result.headers = headers
                        }
                        DispatchQueue.main.async { completionHandler(result) }
                    }
                }
            case .failure(let error):
                JJApiLog("😞😞\(String(describing: response.metrics)) \(String(describing: urlRequest)) \(error.localizedDescription) 🚀end")
                let result = ApiModel<T>(error: (error as NSError).code == NSURLErrorTimedOut ? .timeout : .serverError)
                if let headers = response.response?.headers {
                    result.headers = headers
                }
                DispatchQueue.main.async { completionHandler(result) }
            }
            }
    }

    private static func wrapper(value: [String: Any]) -> [String: Any] {
        var wrapperValue: [String: Any] = ["code": 0, "msg": "success", "data": value]
        if value.keys.contains("data") { // 普通数据
            wrapperValue = value
        } else if value.keys.contains("StatusCode") && value.keys.contains("StatusDesc") { // IM Sports
            let statusCode = value["StatusCode"] as? Int ?? 0
            let statusDesc = value["StatusDesc"] as? String ?? "success"
            wrapperValue["code"] = statusCode == 100 ? 0 : statusCode
            wrapperValue["msg"] = statusDesc
        } else if value.keys.contains("error_code") && value.keys.contains("Data") { // saba Sports
            wrapperValue = [
                "code": value["error_code"] as? Int ?? 0,
                "msg": value["message"] as? String ?? "success",
                "data": value["Data"]!
            ]
        }
        return wrapperValue
    }

    /// 是否设置了代理
    /// - Parameters:
    ///   - url: 请求地址 https://www.apple.com
    /// - Returns: 代理设置情况
    public static func isProxy(url: String) -> Bool {
        guard let proxySettings = CFNetworkCopySystemProxySettings()?.takeUnretainedValue(),
            let url = URL(string: url) else {
            return false
        }
        let proxies = CFNetworkCopyProxiesForURL((url as CFURL), proxySettings).takeUnretainedValue() as NSArray
        guard let settings = proxies.firstObject as? NSDictionary,
            let proxyType = settings.object(forKey: (kCFProxyTypeKey as String)) as? String else {
            return false
        }
        #if DEBUG
        if let hostName = settings.object(forKey: (kCFProxyHostNameKey as String)),
            let port = settings.object(forKey: (kCFProxyPortNumberKey as String)),
            let type = settings.object(forKey: (kCFProxyTypeKey)) {
            JJApiLog("""
                host = \(hostName)
                port = \(port)
                type= \(type)
            """)
        }
        #endif
        return proxyType != (kCFProxyTypeNone as String)
    }
    
    /// 上传图片
    /// - Parameters:
    ///   - urlStr: 上传图片的地址
    ///   - headers: header
    ///   - images: 图片数组
    ///   - keyPath: 数据解析路径
    ///   - completionHandler: 回调
    public static func uploadImage<T: HandyJSON>(urlStr: String, headers: HTTPHeaders, images: [UIImage], keyPath: String? = nil, completionHandler: @escaping(_ result: ApiModel<T>) -> Void) {
        // 创建上传请求
        AF.upload(multipartFormData: { multipartFormData in
            // 遍历图片数组，添加到multipartFormData中
            for (index, image) in images.enumerated() {
                let imageData = image.compressQuality()
                multipartFormData.append(imageData, withName: "files", fileName: "image\(index).jpg", mimeType: "image/jpeg")
            }
        }, to: urlStr, headers: headers).responseJSON(queue: serializationQueue) { response in
            switch response.result {
            case .success(let value):
                JJApiLog("😂😂\(String(describing: response.metrics)) \(urlStr) \(value) 🚀end")
                var result = ApiModel<T>(error: .dataError)
                if let value = value as? [String: Any] {
                    if let model = ApiModel<T>.deserialize(from: wrapper(value: value)) {
                        model.object = T.deserialize(from: model.data as? [String: Any], designatedPath: keyPath)
                        if model.object == nil, let jsonObj = model.data,
                           let data = try? JSONSerialization.data(withJSONObject: jsonObj, options: .fragmentsAllowed),
                           let string = String(data: data, encoding: .utf8) {
                            model.array = [T].deserialize(from: string, designatedPath: keyPath)?.compactMap({ $0 })
                        }
                        result = model
                    }
                    if let headers = response.response?.headers {
                        result.headers = headers
                    }
                    DispatchQueue.main.async {
                        completionHandler(result)
                        if !result.isSuccess, result.isNeedLogin {
                            // 需要登录，发送通知清除本地存储的个人信息，显示登录页
                            NotificationCenter.default.post(name: Notification.Name.User.tokenInvalid, object: nil)
                        }
                        if !result.isSuccess, result.isNeedSetPwd {
                            // 当前账号已存在并且没有密码，需要设置密码
                            NotificationCenter.default.post(name: Notification.Name.User.setPwd, object: nil)
                        }
                    }
                } else {
                    if let value = value as? [Any] {
                        let wrapperValue: [String: Any] = ["code": 0, "msg": "success", "data": value]
                        if let model = ApiModel<T>.deserialize(from: wrapperValue) {
                            model.object = T.deserialize(from: model.data as? [String: Any], designatedPath: keyPath)
                            if model.object == nil, let jsonObj = model.data,
                               let data = try? JSONSerialization.data(withJSONObject: jsonObj, options: .fragmentsAllowed),
                               let string = String(data: data, encoding: .utf8) {
                                model.array = [T].deserialize(from: string, designatedPath: keyPath)?.compactMap({ $0 })
                            }
                            result = model
                        }
                        if let headers = response.response?.headers {
                            result.headers = headers
                        }
                        DispatchQueue.main.async { completionHandler(result) }
                    }
                }
            case .failure(let error):
                JJApiLog("😞😞\(String(describing: response.metrics)) \(urlStr) \(error.localizedDescription) 🚀end")
                let result = ApiModel<T>(error: (error as NSError).code == NSURLErrorTimedOut ? .timeout : .serverError)
                if let headers = response.response?.headers {
                    result.headers = headers
                }
                DispatchQueue.main.async { completionHandler(result) }
            }
            }
    }
}

public class NetworkListener {
    /// 监听网络状态
    public static func start(_ showStatusBar: Bool = false) {
        NetworkReachabilityManager.default?.startListening(onUpdatePerforming: { status in
            DispatchQueue.main.async {
                if showStatusBar {
                    switch status {
                    case .notReachable:
                        msgBar.backgroundColor = .hex("#DC505A")
                        msgBar.show("error_network_proxy_no")
                    case .reachable(let type):
                        msgBar.backgroundColor = .hex("#55F064")
                        let msg = "error_network_proxy_environment" +
                            (type == .cellular ? "error_network_proxy_cellier" : "error_network_proxy_wifi")
                        msgBar.show(msg)
                    default:
                        break
                    }
                }
                switch status {
                case .notReachable:
                    NetErrorView.show()
                case .reachable(let type):
                    JJApiLog("网络状态：\(type)")
                default:
                    break
                }
            }
        })
    }

    private static var _msgBar: MsgBar!
    static var msgBar: MsgBar {
        if let msgBar = _msgBar {
            return msgBar
        }
        _msgBar = MsgBar(frame: CGRect(x: 0, y: 0, width: UIDevice.width, height: UIDevice.statusBarHeight))
        return _msgBar
    }

    class MsgBar: UIView {
        weak var textLabel: UILabel!
        override init(frame: CGRect) {
            super.init(frame: frame)
            backgroundColor = .hex("#DC505A")
            let label = UILabel()
            label.font = .systemFont(ofSize: 13)
            label.textColor = .white
            label.textAlignment = .center
            label.frame = CGRect(x: 0, y: self.height - 20, width: UIDevice.width, height: 20)
            textLabel = label
            addSubview(label)
        }

        func show(_ msg: String) {
            guard let keyWindow = UIApplication.shared.keyWindow else { return }
            keyWindow.subviews.filter({ $0 is MsgBar }).forEach({ $0.removeFromSuperview() })
            textLabel.text = msg
            keyWindow.addSubview(self)
            keyWindow.windowLevel = .alert
            DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
                keyWindow.subviews.filter({ $0 is MsgBar }).forEach({ $0.removeFromSuperview() })
                UIApplication.shared.keyWindow?.windowLevel = .normal
            }
        }

        @available(*, unavailable)
        required init?(coder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
    }
}

class NetErrorView: UIView {
    private static let netErrorView = NetErrorView(frame: CGRect(x: 0, y: 0, width: 360, height: 135))
    static func show() {
        guard let keyWindow = UIApplication.shared.keyWindow else { return }
        netErrorView.superview?.removeFromSuperview()
        netErrorView.removeFromSuperview()
        netErrorView.showCenter(widthFactor: 0.8, tapHide: false)
    }
    override init(frame: CGRect) {
        super.init(frame: frame)
        backgroundColor = .white
        clipsToBounds = true
        layer.cornerRadius = 8
        let currentBundle = Bundle(for: NetErrorView.self)
        let imageView = UIImageView(image: UIImage(named: "no_network", in: currentBundle, compatibleWith: nil))
        addSubview(imageView)
        imageView.snp.makeConstraints { make in
            make.width.equalTo(57)
            make.height.equalTo(44)
            make.top.equalTo(32)
            make.centerX.equalToSuperview()
        }
        let closeBtn = UIButton()
        closeBtn.setImage(UIImage(named: "network_close", in: currentBundle, compatibleWith: nil), for: .normal)
        closeBtn.addTarget(self, action: #selector(closeAction), for: .touchUpInside)
        addSubview(closeBtn)
        closeBtn.snp.makeConstraints { make in
            make.width.height.equalTo(40)
            make.top.right.equalToSuperview()
        }
        let label = UILabel()
        label.text = "没有网络了！请检查网络配置！"
        label.textColor = .hex("#6C6C6C")
        label.font = .systemFont(ofSize: 14)
        addSubview(label)
        label.snp.makeConstraints { make in
            make.top.equalTo(imageView.snp.bottom).offset(18)
            make.centerX.equalToSuperview()
            make.bottom.equalTo(-20).priority(.low)
        }
    }

    @objc func closeAction() {
        self.hiddenCenter()
    }

    @available(*, unavailable)
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

fileprivate func JJApiLog<T>(_ message: T, file: String = #file, method: String = #function, line: Int = #line) {
    #if DEBUG
    print("\nNetworkLog:\n##**🚀🚀🚀=============== start ===============>\n\((file as NSString).lastPathComponent)[\(line)], \(method): \(message)\n<=============== end ===============🎉🎉🎉**##\n")
    #endif
}
